// 版权所有 Epic Games, Inc. 保留所有权利。

#include "LyraGameInstance.h"

#include "CommonSessionSubsystem.h"
#include "CommonUserSubsystem.h"
#include "Components/GameFrameworkComponentManager.h"
#include "HAL/IConsoleManager.h"
#include "LyraGameplayTags.h"
#include "Misc/Paths.h"
#include "Player/LyraPlayerController.h"
#include "Player/LyraLocalPlayer.h"
#include "GameFramework/PlayerState.h"

#if UE_WITH_DTLS
#include "DTLSCertStore.h"
#include "DTLSHandlerComponent.h"
#include "Misc/FileHelper.h"
#endif // UE_WITH_DTLS

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraGameInstance)

namespace Lyra
{
	static bool bTestEncryption = false; // 测试加密标志
	static FAutoConsoleVariableRef CVarLyraTestEncryption( // 控制台变量引用
		TEXT("Lyra.TestEncryption"),
		bTestEncryption,
		TEXT("如果为true，客户端将在加入服务器请求时发送加密令牌，并尝试使用调试密钥加密连接。这不安全，仅用于演示目的。"),
		ECVF_Default);

#if UE_WITH_DTLS
	static bool bUseDTLSEncryption = false; // 使用DTLS加密标志
	static FAutoConsoleVariableRef CVarLyraUseDTLSEncryption( // DTLS加密控制台变量
		TEXT("Lyra.UseDTLSEncryption"),
		bUseDTLSEncryption,
		TEXT("如果使用Lyra.TestEncryption和DTLS数据包处理程序，则设置为true。"),
		ECVF_Default);

	/* 用于在同一设备上测试多个游戏实例（桌面构建） */
	static bool bTestDTLSFingerprint = false; // 测试DTLS指纹标志
	static FAutoConsoleVariableRef CVarLyraTestDTLSFingerprint( // DTLS指纹测试控制台变量
		TEXT("Lyra.TestDTLSFingerprint"),
		bTestDTLSFingerprint,
		TEXT("如果为true且使用DTLS加密，将为每个连接生成唯一证书，指纹将写入文件以模拟通过在线服务传递。"),
		ECVF_Default);

#if !UE_BUILD_SHIPPING
	static FAutoConsoleCommandWithWorldAndArgs CmdGenerateDTLSCertificate( // 生成DTLS证书命令
		TEXT("GenerateDTLSCertificate"),
		TEXT("生成用于测试的DTLS自签名证书并导出为PEM格式。"),
		FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray<FString>& InArgs, UWorld* InWorld)
			{
				if (InArgs.Num() == 1) // 检查参数数量
				{
					const FString& CertName = InArgs[0]; // 获取证书名称

					FTimespan CertExpire = FTimespan::FromDays(365); // 设置证书过期时间
					TSharedPtr<FDTLSCertificate> Cert = FDTLSCertStore::Get().CreateCert(CertExpire, CertName); // 创建证书
					if (Cert.IsValid()) // 检查证书是否有效
					{
						const FString CertPath = FPaths::ProjectContentDir() / TEXT("DTLS") / FPaths::MakeValidFileName(FString::Printf(TEXT("%s.pem"), *CertName)); // 生成证书路径

						if (!Cert->ExportCertificate(CertPath)) // 导出证书
						{
							UE_LOG(LogTemp, Error, TEXT("GenerateDTLSCertificate: 导出证书失败。")); // 记录错误日志
						}
					}
					else
					{
						UE_LOG(LogTemp, Error, TEXT("GenerateDTLSCertificate: 生成证书失败。")); // 记录错误日志
					}
				}
				else
				{
					UE_LOG(LogTemp, Error, TEXT("GenerateDTLSCertificate: 无效参数。")); // 记录错误日志
				}
			}));
#endif // UE_BUILD_SHIPPING
#endif // UE_WITH_DTLS
};

/**
 * 构造函数
 * @param ObjectInitializer 对象初始化器
 */
ULyraGameInstance::ULyraGameInstance(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer) // 调用父类构造函数
{
}

/**
 * 初始化游戏实例
 */
void ULyraGameInstance::Init()
{
	Super::Init(); // 调用父类初始化函数

	// 注册自定义初始化状态
	UGameFrameworkComponentManager* ComponentManager = GetSubsystem<UGameFrameworkComponentManager>(this); // 获取游戏框架组件管理器子系统

	if (ensure(ComponentManager)) // 确保组件管理器指针有效，如果无效则断言
	{
		// 注册各个初始化状态及其依赖关系
		ComponentManager->RegisterInitState(LyraGameplayTags::InitState_Spawned, false, FGameplayTag()); // 注册生成状态，无前置依赖
		ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataAvailable, false, LyraGameplayTags::InitState_Spawned); // 注册数据可用状态，依赖生成状态
		ComponentManager->RegisterInitState(LyraGameplayTags::InitState_DataInitialized, false, LyraGameplayTags::InitState_DataAvailable); // 注册数据初始化状态，依赖数据可用状态
		ComponentManager->RegisterInitState(LyraGameplayTags::InitState_GameplayReady, false, LyraGameplayTags::InitState_DataInitialized); // 注册游戏准备就绪状态，依赖数据初始化状态
	}

	// 使用设定值初始化调试加密密钥（AES256算法）。注意：这不安全，仅用于示例目的。
	DebugTestEncryptionKey.SetNum(32); // 设置调试测试加密密钥数组大小为32字节（AES256需要256位密钥）

	for (int32 i = 0; i < DebugTestEncryptionKey.Num(); ++i) // 遍历调试测试加密密钥数组的每个元素
	{
		DebugTestEncryptionKey[i] = uint8(i); // 将密钥的每个字节设置为对应的索引值（0,1,2,...,31）
	}

	if (UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>()) // 获取通用会话子系统并检查是否有效
	{
		SessionSubsystem->OnPreClientTravelEvent.AddUObject(this, &ULyraGameInstance::OnPreClientTravelToSession); // 将客户端旅行前事件绑定到当前对象的OnPreClientTravelToSession函数
	}
}

/**
 * 关闭游戏实例
 */
void ULyraGameInstance::Shutdown()
{
	if (UCommonSessionSubsystem* SessionSubsystem = GetSubsystem<UCommonSessionSubsystem>()) // 获取会话子系统
	{
		SessionSubsystem->OnPreClientTravelEvent.RemoveAll(this); // 移除所有客户端旅行前事件绑定
	}

	Super::Shutdown(); // 调用父类关闭
}

/**
 * 获取主玩家控制器
 * @return 返回主玩家控制器指针
 */
ALyraPlayerController* ULyraGameInstance::GetPrimaryPlayerController() const
{
	return Cast<ALyraPlayerController>(Super::GetPrimaryPlayerController(false)); // 转换并返回主玩家控制器
}

/**
 * 检查是否可以加入请求的会话
 * @return 如果可以加入返回true，否则返回false
 */
bool ULyraGameInstance::CanJoinRequestedSession() const
{
	// 临时第一遍：总是返回true
	// 这将进一步完善以检查玩家状态
	if (!Super::CanJoinRequestedSession()) // 调用父类检查
	{
		return false; // 如果父类不允许则返回false
	}
	return true; // 返回true
}

/**
 * 处理用户初始化完成
 * @param UserInfo 用户信息
 * @param bSuccess 是否成功
 * @param Error 错误信息
 * @param RequestedPrivilege 请求的权限
 * @param OnlineContext 在线上下文
 */
void ULyraGameInstance::HandlerUserInitialized(const UCommonUserInfo* UserInfo, bool bSuccess, FText Error, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext OnlineContext)
{
	Super::HandlerUserInitialized(UserInfo, bSuccess, Error, RequestedPrivilege, OnlineContext); // 调用父类处理

	// 如果登录成功，告诉本地玩家加载他们的设置
	if (bSuccess && ensure(UserInfo)) // 检查是否成功且用户信息有效
	{
		ULyraLocalPlayer* LocalPlayer = Cast<ULyraLocalPlayer>(GetLocalPlayerByIndex(UserInfo->LocalPlayerIndex)); // 获取本地玩家

		// 专用服务器用户不会附加本地玩家
		if (LocalPlayer) // 检查本地玩家是否存在
		{
			LocalPlayer->LoadSharedSettingsFromDisk(); // 从磁盘加载共享设置
		}
	}
}

/**
 * 接收网络加密令牌
 * @param EncryptionToken 加密令牌
 * @param Delegate 加密密钥响应委托
 */
void ULyraGameInstance::ReceivedNetworkEncryptionToken(const FString& EncryptionToken, const FOnEncryptionKeyResponse& Delegate)
{
	// 这是一个简单的实现，演示使用硬编码密钥加密游戏流量。
	// 对于完整的实现，您可能希望从安全源（如通过HTTPS的Web服务）检索加密密钥。
	// 这可以在此函数中完成，甚至是异步的 - 只需在知道密钥后调用传入的响应委托。
	// EncryptionToken的内容由用户决定，但通常包含用于生成唯一加密密钥的信息，例如用户和/或会话ID。

	FEncryptionKeyResponse Response(EEncryptionResponse::Failure, TEXT("未知加密失败")); // 初始化响应

	if (EncryptionToken.IsEmpty()) // 检查加密令牌是否为空
	{
		Response.Response = EEncryptionResponse::InvalidToken; // 设置响应为无效令牌
		Response.ErrorMsg = TEXT("加密令牌为空。"); // 设置错误消息
	}
	else
	{
#if UE_WITH_DTLS
		if (Lyra::bUseDTLSEncryption) // 检查是否使用DTLS加密
		{
			TSharedPtr<FDTLSCertificate> Cert; // 证书指针

			if (Lyra::bTestDTLSFingerprint) // 检查是否测试DTLS指纹
			{
				// 为此标识符生成服务器证书，发布指纹
				FTimespan CertExpire = FTimespan::FromHours(4); // 设置证书过期时间
				Cert = FDTLSCertStore::Get().CreateCert(CertExpire, EncryptionToken); // 创建证书
			}
			else
			{
				// 从磁盘加载证书用于测试目的（永远不要在生产环境中使用）
				const FString CertPath = FPaths::ProjectContentDir() / TEXT("DTLS") / TEXT("LyraTest.pem"); // 证书路径

				Cert = FDTLSCertStore::Get().GetCert(EncryptionToken); // 获取证书

				if (!Cert.IsValid()) // 检查证书是否无效
				{
					Cert = FDTLSCertStore::Get().ImportCert(CertPath, EncryptionToken); // 导入证书
				}
			}

			if (Cert.IsValid()) // 检查证书是否有效
			{
				if (Lyra::bTestDTLSFingerprint) // 检查是否测试DTLS指纹
				{
					// 指纹应发布到安全的Web服务以供发现
					// 为本地测试写入磁盘
					TArrayView<const uint8> Fingerprint = Cert->GetFingerprint(); // 获取指纹

					FString DebugFile = FPaths::Combine(*FPaths::ProjectSavedDir(), TEXT("DTLS")) / FPaths::MakeValidFileName(EncryptionToken) + TEXT("_server.txt"); // 调试文件路径

					FString FingerprintStr = BytesToHex(Fingerprint.GetData(), Fingerprint.Num()); // 转换为十六进制字符串
					FFileHelper::SaveStringToFile(FingerprintStr, *DebugFile); // 保存到文件
				}

				// 服务器当前只需要标识符
				Response.EncryptionData.Identifier = EncryptionToken; // 设置标识符
				Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥

				Response.Response = EEncryptionResponse::Success; // 设置响应为成功
			}
			else
			{
				Response.Response = EEncryptionResponse::Failure; // 设置响应为失败
				Response.ErrorMsg = TEXT("无法获取证书。"); // 设置错误消息
			}
		}
		else
#endif // UE_WITH_DTLS
		{
			Response.Response = EEncryptionResponse::Success; // 设置响应为成功
			Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥
		}
	}

	Delegate.ExecuteIfBound(Response); // 执行委托
}

/**
 * 接收网络加密确认
 * @param Delegate 加密密钥响应委托
 */
void ULyraGameInstance::ReceivedNetworkEncryptionAck(const FOnEncryptionKeyResponse& Delegate)
{
	// 这是一个简单的实现，演示使用硬编码密钥加密游戏流量。
	// 对于完整的实现，您可能希望从安全源（如通过HTTPS的Web服务）检索加密密钥。
	// 这可以在此函数中完成，甚至是异步的 - 只需在知道密钥后调用传入的响应委托。

	FEncryptionKeyResponse Response; // 加密密钥响应

#if UE_WITH_DTLS
	if (Lyra::bUseDTLSEncryption) // 检查是否使用DTLS加密
	{
		Response.Response = EEncryptionResponse::Failure; // 设置响应为失败

		APlayerController* const PlayerController = GetFirstLocalPlayerController(); // 获取第一个本地玩家控制器

		if (PlayerController && PlayerController->PlayerState && PlayerController->PlayerState->GetUniqueId().IsValid()) // 检查玩家控制器和玩家状态
		{
			const FUniqueNetIdRepl& PlayerUniqueId = PlayerController->PlayerState->GetUniqueId(); // 获取玩家唯一ID

			// 理想情况下，加密令牌应直接传入，而不是尝试重新构建它
			const FString EncryptionToken = PlayerUniqueId.ToString(); // 转换为字符串

			Response.EncryptionData.Identifier = EncryptionToken; // 设置标识符

			// 服务器的指纹应从安全服务中拉取
			if (Lyra::bTestDTLSFingerprint) // 检查是否测试DTLS指纹
			{
				// 但为了测试目的...
				FString DebugFile = FPaths::Combine(*FPaths::ProjectSavedDir(), TEXT("DTLS")) / FPaths::MakeValidFileName(EncryptionToken) + TEXT("_server.txt"); // 调试文件路径
				FString FingerprintStr; // 指纹字符串
				FFileHelper::LoadFileToString(FingerprintStr, *DebugFile); // 从文件加载指纹

				Response.EncryptionData.Fingerprint.AddUninitialized(FingerprintStr.Len() / 2); // 分配指纹数组大小
				HexToBytes(FingerprintStr, Response.EncryptionData.Fingerprint.GetData()); // 十六进制字符串转换为字节
			}
			else
			{
				// 从磁盘拉取预期指纹用于测试，这应来自安全服务
				const FString CertPath = FPaths::ProjectContentDir() / TEXT("DTLS") / TEXT("LyraTest.pem"); // 证书路径

				TSharedPtr<FDTLSCertificate> Cert = FDTLSCertStore::Get().GetCert(EncryptionToken); // 获取证书
				if (!Cert.IsValid()) // 检查证书是否无效
				{
					Cert = FDTLSCertStore::Get().ImportCert(CertPath, EncryptionToken); // 导入证书
				}

				if (Cert.IsValid()) // 检查证书是否有效
				{
					TArrayView<const uint8> Fingerprint = Cert->GetFingerprint(); // 获取指纹

					Response.EncryptionData.Fingerprint = Fingerprint; // 设置指纹
				}
				else
				{
					Response.Response = EEncryptionResponse::Failure; // 设置响应为失败
					Response.ErrorMsg = TEXT("无法获取证书。"); // 设置错误消息
				}
			}

			Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥

			Response.Response = EEncryptionResponse::Success; // 设置响应为成功
		}
	}
	else
#endif // UE_WITH_DTLS
	{
		Response.Response = EEncryptionResponse::Success; // 设置响应为成功
		Response.EncryptionData.Key = DebugTestEncryptionKey; // 设置密钥
	}

	Delegate.ExecuteIfBound(Response); // 执行委托
}

/**
 * 在客户端旅行到会话前的处理
 * @param URL 旅行URL
 */
void ULyraGameInstance::OnPreClientTravelToSession(FString& URL)
{
	// 如果需要，添加调试加密令牌。
	if (Lyra::bTestEncryption) // 检查是否测试加密
	{
#if UE_WITH_DTLS
		if (Lyra::bUseDTLSEncryption) // 检查是否使用DTLS加密
		{
			APlayerController* const PlayerController = GetFirstLocalPlayerController(); // 获取第一个本地玩家控制器

			if (PlayerController && PlayerController->PlayerState && PlayerController->PlayerState->GetUniqueId().IsValid()) // 检查玩家控制器和玩家状态
			{
				const FUniqueNetIdRepl& PlayerUniqueId = PlayerController->PlayerState->GetUniqueId(); // 获取玩家唯一ID
				const FString EncryptionToken = PlayerUniqueId.ToString(); // 转换为字符串

				URL += TEXT("?EncryptionToken=") + EncryptionToken; // 添加加密令牌到URL
			}
		}
		else
#endif // UE_WITH_DTLS
		{
			// 这只是一个用于测试/调试的值，服务器将使用相同的密钥，无论令牌值如何。
			// 但令牌可以是用户ID和/或会话ID，用于为每个用户和/或会话生成唯一密钥（如果需要）。
			URL += TEXT("?EncryptionToken=1"); // 添加默认加密令牌到URL
		}
	}
}